package query.update

import ast.expr.SqlIdentifierExpr
import ast.expr.SqlPropertyExpr
import ast.statement.update.SqlUpdate
import database.DB
import dsl.Query
import dsl.QueryTableColumn
import dsl.TableSchema
import dsl.column
import query.ReviseQuery
import util.toSqlString
import visitor.getExpr
import visitor.getQueryExpr
import java.sql.Connection
import java.sql.SQLException
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.declaredMemberProperties

/**
 * update语句dsl类
 * @property db DB 数据库类型
 * @property conn Connection? 数据库连接
 * @property isTransaction Boolean 是否是事务
 * @property sqlUpdate SqlUpdate update语法树
 */
class Update(
    var db: DB = DB.MYSQL,
    override var conn: Connection? = null,
    override var isTransaction: Boolean = false
) : ReviseQuery() {
    constructor(db: DB) : this(db, null, false)

    private var sqlUpdate = SqlUpdate()

    /**
     * update子句
     * 例如：Update() update "t1"
     * @param table String 表名
     * @return Update 更新dsl
     */
    infix fun update(table: String): Update {
        this.sqlUpdate.table = SqlIdentifierExpr(table)
        return this
    }

    /**
     * update子句
     * 例如：db update Table
     * @param table TableSchema 实体类伴生对象名
     * @return Update 更新dsl
     */
    infix fun update(table: TableSchema): Update {
        return update(table.tableName)
    }

    /**
     * 生成按主键更新其他字段的值的sql（空字段会被忽略）
     * 例如：db update entity
     * @param entity Any 实体对象
     * @return Update 更新dsl
     */
    infix fun update(entity: Any): Update {
        val clazz = entity::class
        val fields = clazz.declaredMemberProperties
            .map { it.name to it.getter.call(entity) }
            .toMap()

        val companion = clazz.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val companionClass = companion::class
        val table = companion as TableSchema
        update(table.tableName)

        val columns = companionClass.declaredMemberProperties
            .map { it.name to it.getter.call(companion) }
            .filter { it.second is QueryTableColumn }
            .map { it.first to (it.second as QueryTableColumn) }
            .toMap()

        val pkCols = columns
            .filter { it.value.primaryKey }
            .map { it.value.column to fields[it.key] }

        if (pkCols.isEmpty()) {
            throw SQLException("实体类的伴生对象中没有设置主键字段")
        }

        pkCols.forEach {
            if (it.second == null) {
                throw SQLException("主键为空")
            } else {
                where(column(it.first) eq it.second)
            }
        }

        val setCols = columns
            .filter { !it.value.primaryKey }
            .map { it.value.column to fields[it.key] }
            .filter { it.second != null }
            .map { column(it.first) to it.second!! }

        set(setCols)

        return this
    }

    /**
     * set子句
     * 例如：db update Table set (Table.c1 to 1)
     * @param item Pair<Query, Any?> 更新列和值的二元组，其中值的合法的类型有Number、String、Date、List、Boolean以及null和Query表达式
     * @return Update 更新dsl
     */
    infix fun set(item: Pair<Query, Any?>): Update {
        val (column, value) = item
        var columnExpr = getQueryExpr(column, this.db).expr
        if (columnExpr is SqlPropertyExpr) {
            columnExpr = SqlIdentifierExpr(columnExpr.name)
        }

        val valueExpr = if (value is Query) {
            getQueryExpr(value, this.db).expr
        } else {
            getExpr(value)
        }
        sqlUpdate.setList.add(columnExpr to valueExpr)

        return this
    }

    /**
     * set子句
     * 例如：db update Table set (listOf(Table.c1 to 1, Table.c2 to "xxx"))
     * @param items List<Pair<Query, Any?>> 更新列和值的二元组列表，其中值的合法的类型有Number、String、Date、List、Boolean以及null和Query表达式
     * @return Update 更新dsl
     */
    infix fun set(items: List<Pair<Query, Any?>>): Update {
        items.forEach {
            val (column, value) = it
            var columnExpr = getQueryExpr(column, this.db).expr
            if (columnExpr is SqlPropertyExpr) {
                columnExpr = SqlIdentifierExpr(columnExpr.name)
            }

            val valueExpr = if (value is Query) {
                getQueryExpr(value, this.db).expr
            } else {
                getExpr(value)
            }
            sqlUpdate.setList.add(columnExpr to valueExpr)
        }

        return this
    }

    /**
     * set子句
     * 例如：db.update(Table).set(Table.c1 to 1, Table.c2 to "xxx")
     * @param items Array<out Pair<Query, Any?>> 更新列和值的二元组列表，其中值的合法的类型有Number、String、Date、List、Boolean以及null和Query表达式
     * @return Update 更新dsl
     */
    fun set(vararg items: Pair<Query, Any?>): Update {
        items.forEach {
            val (column, value) = it
            var columnExpr = getQueryExpr(column, this.db).expr
            if (columnExpr is SqlPropertyExpr) {
                columnExpr = SqlIdentifierExpr(columnExpr.name)
            }

            val valueExpr = if (value is Query) {
                getQueryExpr(value, this.db).expr
            } else {
                getExpr(value)
            }
            sqlUpdate.setList.add(columnExpr to valueExpr)
        }

        return this
    }

    /**
     * 列自增
     * 例如：db.update(Table).incr(Table.c1, 1)
     * @param column Query 更新列
     * @param value Int 自增数量
     * @return Update 更新dsl
     */
    fun incr(column: Query, value: Int): Update {
        var columnExpr = getQueryExpr(column, this.db).expr
        if (columnExpr is SqlPropertyExpr) {
            columnExpr = SqlIdentifierExpr(columnExpr.name)
        }
        sqlUpdate.setList.add(columnExpr to getQueryExpr(column + value, this.db).expr)
        return this
    }

    /**
     * 列自增1
     * 例如：db update Table incr Table.c1
     * @param column Query 更新列
     * @return Update 更新dsl
     */
    infix fun incr(column: Query): Update {
        return incr(column, 1)
    }

    /**
     * 列自减
     * 例如：db.update(Table).decr(Table.c1, 1)
     * @param column Query 更新列
     * @param value Int 自减数量
     * @return Update 更新dsl
     */
    fun decr(column: Query, value: Int): Update {
        var columnExpr = getQueryExpr(column, this.db).expr
        if (columnExpr is SqlPropertyExpr) {
            columnExpr = SqlIdentifierExpr(columnExpr.name)
        }
        sqlUpdate.setList.add(columnExpr to getQueryExpr(column - value, this.db).expr)
        return this
    }

    /**
     * 列自减1
     * 例如：db update Table decr Table.c1
     * @param column Query 更新列
     * @return Update 更新dsl
     */
    infix fun decr(column: Query): Update {
        return decr(column, 1)
    }

    /**
     * where子句（如果有多个where调用，会使用AND拼接条件）
     * 例如：db update Table set (Table.c1 to 1) where (Table.c2 eq "xxx")
     * @param condition Query 条件表达式
     * @return Update 更新dsl
     */
    infix fun where(condition: Query): Update {
        this.sqlUpdate.addCondition(getQueryExpr(condition, this.db).expr)
        return this
    }

    /**
     * where子句（当前面的lambda返回值为true的时候将条件拼接进sql）
     * 例如：db.update(Table).set(Table.c1 to 1).where({ true }, Table.c2 eq "xxx")
     * @param test Function0<Boolean> 当此参数返回值为true时将condition拼接进sql
     * @param condition Query 条件表达式
     * @return Update 更新dsl
     */
    fun where(test: () -> Boolean, condition: Query): Update {
        if (test()) {
            where(condition)
        }

        return this
    }

    /**
     * where子句（当前面的参数为true的时候将条件拼接进sql）
     * 例如：db.update(Table).set(Table.c1 to 1).where(true, Table.c2 eq "xxx")
     * @param test Boolean 当此参数为true时将condition拼接进sql
     * @param condition Query 条件表达式
     * @return Update 更新dsl
     */
    fun where(test: Boolean, condition: Query): Update {
        if (test) {
            where(condition)
        }

        return this
    }

    /**
     * 生成sql
     * @return String sql语句
     */
    override fun sql() = toSqlString(sqlUpdate, db)
}